/*
MIT License

Copyright(c) 2020 Evgeny Pereguda

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include "Player.h"
#include "resource.h"

#include "CaptureManagerVideoRendererFactory.h"
#include "Common\CaptureManagerTypeInfo.h"

#include "Direct3D9Renderer.h"
#include "RendererFactory.h"

#include <assert.h>

#pragma comment(lib, "strmiids")
#pragma comment(lib, "evr") 
#pragma comment(lib, "mf") 
#pragma comment(lib, "mfplat") 
#pragma comment(lib, "mfplay") 
#pragma comment(lib, "mfreadwrite") 
#pragma comment(lib, "mfuuid")  
#pragma comment(lib, "Shlwapi") 
#pragma comment(lib, "Ole32")


template <class Q>
HRESULT GetEventObject(IMFMediaEvent *pEvent, Q **ppObject)
{
	*ppObject = NULL;   // zero output

	PROPVARIANT var;
	HRESULT hr = pEvent->GetValue(&var);
	if (SUCCEEDED(hr))
	{
		if (var.vt == VT_UNKNOWN)
		{
			hr = var.punkVal->QueryInterface(ppObject);
		}
		else
		{
			hr = MF_E_INVALIDTYPE;
		}
		PropVariantClear(&var);
	}
	return hr;
}

HRESULT CreateMediaSource(PCWSTR pszURL, IMFMediaSource **ppSource);
HRESULT CreateMediaSource(PCWSTR sURL1, PCWSTR sURL2, IMFMediaSource **ppSource);

HRESULT CreatePlaybackTopology(
	IMFMediaSource *pSource,
	IMFPresentationDescriptor *pPD, 
	HWND hVideoWnd, 
	IMFTopology **ppTopology);

HRESULT CreatePlaybackTopology(
	IMFMediaSource *pSource,          // Media source.
	IMFPresentationDescriptor *pPD,   // Presentation descriptor.
	IMFTopologyNode* pVideoRendererActivate,                   // Video renderer activate.
	IMFTopology **ppTopology);

DWORD gStreamCount = 0;

//  Static class method to create the CPlayer object.

HRESULT CPlayer::CreateInstance(
	HWND hVideo,                  // Video window.
	HWND hEvent,                  // Window to receive notifications.
	CPlayer **ppPlayer)           // Receives a pointer to the CPlayer object.
{
	if (ppPlayer == NULL)
	{
		return E_POINTER;
	}

	CPlayer *pPlayer = new (std::nothrow) CPlayer(hVideo, hEvent);
	if (pPlayer == NULL)
	{
		return E_OUTOFMEMORY;
	}

	HRESULT hr = pPlayer->Initialize();
	if (SUCCEEDED(hr))
	{
		*ppPlayer = pPlayer;
	}
	else
	{
		pPlayer->Release();
	}
	return hr;
}

HRESULT CPlayer::Initialize()
{
	// Start up Media Foundation platform.
	HRESULT hr = MFStartup(MF_VERSION);
	if (SUCCEEDED(hr))
	{
		m_hCloseEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
		if (m_hCloseEvent == NULL)
		{
			hr = HRESULT_FROM_WIN32(GetLastError());
		}
	}
		
	return hr;
}

CPlayer::CPlayer(HWND hVideo, HWND hEvent) :
m_pSession(NULL),
m_pSource(NULL),
m_pVideoDisplay(NULL),
m_hwndVideo(hVideo),
m_hwndEvent(hEvent),
m_state(Closed),
m_hCloseEvent(NULL),
m_nRefCount(1),
m_uiRenderID(ID_WINDOW_HANDLER)
{
}

CPlayer::~CPlayer()
{
	assert(m_pSession == NULL);
	// If FALSE, the app did not call Shutdown().

	// When CPlayer calls IMediaEventGenerator::BeginGetEvent on the
	// media session, it causes the media session to hold a reference 
	// count on the CPlayer. 

	// This creates a circular reference count between CPlayer and the 
	// media session. Calling Shutdown breaks the circular reference 
	// count.

	// If CreateInstance fails, the application will not call 
	// Shutdown. To handle that case, call Shutdown in the destructor. 

	Shutdown();
}

// IUnknown methods

HRESULT CPlayer::QueryInterface(REFIID riid, void** ppv)
{
	static const QITAB qit[] =
	{
		QITABENT(CPlayer, IMFAsyncCallback),
		{ 0 }
	};
	return QISearch(this, qit, riid, ppv);
}

ULONG CPlayer::AddRef()
{
	return InterlockedIncrement(&m_nRefCount);
}

ULONG CPlayer::Release()
{
	ULONG uCount = InterlockedDecrement(&m_nRefCount);
	if (uCount == 0)
	{
		delete this;
	}
	return uCount;
}

void CPlayer::setLibraryType(UINT aLibraryID)
{
	CaptureManagerVideoRendererFactory::getInstance()->selectLoader(aLibraryID & 0x000F);
}

void CPlayer::setRenderType(UINT aRenderID)
{
	m_uiRenderID = aRenderID;
}

void CPlayer::Refresh()
{
	if (m_Renderer)
		m_Renderer->presents();

}

//  Open a URL for playback.
HRESULT CPlayer::OpenURL(const WCHAR *sURL)
{
	CComPtrCustom<IMFActivate> lMixerActivate;

	CComQIPtrCustom<IMFTopologyNode> lRenderMFSink;

	CComPtrCustom<IUnknown> lUnkTopologyNode;

	CComPtrCustom<IUnknown> lRenderTarget;

	CComPtrCustom<IUnknown> lUnkEVRStreamControl;

	CComPtrCustom<IEVRStreamControl> lEVRStreamControl;
		
	if (m_Renderer)
		m_Renderer->shutdown();

	m_Renderer.Release();

	HRESULT hr = CloseSession();


	// 1. Create a new media session.
	// 2. Create the media source.
	// 3. Create the topology.
	// 4. Queue the topology [asynchronous]
	// 5. Start playback [asynchronous - does not happen in this method.]

	IMFTopology *pTopology = NULL;
	IMFPresentationDescriptor* pSourcePD = NULL;
	IMFActivate* pVideoRendererActivate = NULL;

	// Create the media session.
	hr = CreateSession();
	if (FAILED(hr))
	{
		goto done;
	}

	DWORD lMaxCount;

	CaptureManagerVideoRendererFactory::getInstance()->getMaxVideoRenderStreamCount(&lMaxCount);

	// Create the media source.
	hr = CreateMediaSource(sURL, &m_pSource);
	//hr = CreateMediaSource(sURL, sURL, &m_pSource);
	if (FAILED(hr))
	{
		goto done;
	}

	// Create the presentation descriptor for the media source.
	hr = m_pSource->CreatePresentationDescriptor(&pSourcePD);
	if (FAILED(hr))
	{
		goto done;
	}

	// select video renderer context
	switch (m_uiRenderID & 0x00F0)
	{
	case (ID_DX9_TEXTURE & 0x00F0):
	{
		RendererFactory::createRenderer(
			RendererFactory::Directx3D9,
			m_hwndVideo,
			&m_Renderer);
	}
		break;	
	default:
		CaptureManagerVideoRendererFactory::getInstance()->getRenderer(m_hwndVideo, &lUnkTopologyNode);
		break;
	}
	
	// select renderer type target
	switch (m_uiRenderID & 0x000F)
	{
	case 1:
	{
		m_Renderer->getRenderTarget(IRenderer::Texture, &lRenderTarget);

		CaptureManagerVideoRendererFactory::getInstance()->getRenderer(lRenderTarget, &lUnkTopologyNode);
	}

		break;
	case 2:
	{
		m_Renderer->getRenderTarget(IRenderer::SwapChain, &lRenderTarget);

		CaptureManagerVideoRendererFactory::getInstance()->getRenderer(lRenderTarget, &lUnkTopologyNode);
	}

		break;
	default:
		break;
	}

	lRenderMFSink = lUnkTopologyNode;
			
	// Create a partial topology.
	hr = CreatePlaybackTopology(m_pSource, pSourcePD, lRenderMFSink, &pTopology);
	if (FAILED(hr))
	{
		goto done;
	}

	hr = CaptureManagerVideoRendererFactory::getInstance()->getEVRControl(&lUnkEVRStreamControl);

	if (FAILED(hr))
	{
		goto done;
	}

	hr = lUnkEVRStreamControl->QueryInterface(IID_PPV_ARGS(&lEVRStreamControl));

	if (FAILED(hr))
	{
		goto done;
	}

	hr = lEVRStreamControl->setPosition(lRenderMFSink,
		0.0f,
		0.5f,
		0.5f,
		1.0f);

	if (FAILED(hr))
	{
		goto done;
	}

	// Set the topology on the media session.
	hr = m_pSession->SetTopology(0, pTopology);
	if (FAILED(hr))
	{
		goto done;
	}

	m_state = OpenPending;

	// If SetTopology succeeds, the media session will queue an 
	// MESessionTopologySet event.

done:
	if (FAILED(hr))
	{
		m_state = Closed;
	}

	SafeRelease(&pSourcePD);
	SafeRelease(&pTopology); 
	SafeRelease(&pVideoRendererActivate);
	return hr;
}

//  Pause playback.
HRESULT CPlayer::Pause()
{
	if (m_state != Started)
	{
		return MF_E_INVALIDREQUEST;
	}
	if (m_pSession == NULL || m_pSource == NULL)
	{
		return E_UNEXPECTED;
	}

	HRESULT hr = m_pSession->Pause();
	if (SUCCEEDED(hr))
	{
		m_state = Paused;
	}

	return hr;
}

// Stop playback.
HRESULT CPlayer::Stop()
{
	if (m_state != Started && m_state != Paused)
	{
		return MF_E_INVALIDREQUEST;
	}
	if (m_pSession == NULL)
	{
		return E_UNEXPECTED;
	}

	HRESULT hr = m_pSession->Stop();
	if (SUCCEEDED(hr))
	{
		m_state = Stopped;
	}
	return hr;
}

//  Repaint the video window. Call this method on WM_PAINT.

HRESULT CPlayer::Repaint()
{
	if (m_pVideoDisplay)
	{
		return m_pVideoDisplay->RepaintVideo();
	}
	else
	{
		return S_OK;
	}
}

//  Resize the video rectangle.
//
//  Call this method if the size of the video window changes.

HRESULT CPlayer::ResizeVideo(WORD width, WORD height)
{
	if (m_pVideoDisplay)
	{
		// Set the destination rectangle.
		// Leave the default source rectangle (0,0,1,1).

		RECT rcDest = { 0, 0, width, height };

		return m_pVideoDisplay->SetVideoPosition(NULL, &rcDest);
	}
	else
	{
		return S_OK;
	}
}

//  Callback for the asynchronous BeginGetEvent method.

HRESULT CPlayer::Invoke(IMFAsyncResult *pResult)
{
	MediaEventType meType = MEUnknown;  // Event type

	IMFMediaEvent *pEvent = NULL;

	// Get the event from the event queue.
	HRESULT hr = m_pSession->EndGetEvent(pResult, &pEvent);
	if (FAILED(hr))
	{
		goto done;
	}

	// Get the event type. 
	hr = pEvent->GetType(&meType);
	if (FAILED(hr))
	{
		goto done;
	}

	if (meType == MESessionClosed)
	{
		// The session was closed. 
		// The application is waiting on the m_hCloseEvent event handle. 
		SetEvent(m_hCloseEvent);
	}
	else
	{
		// For all other events, get the next event in the queue.
		hr = m_pSession->BeginGetEvent(this, NULL);
		if (FAILED(hr))
		{
			goto done;
		}
	}

	// Check the application state. 

	// If a call to IMFMediaSession::Close is pending, it means the 
	// application is waiting on the m_hCloseEvent event and
	// the application's message loop is blocked. 

	// Otherwise, post a private window message to the application. 

	if (m_state != Closing)
	{
		// Leave a reference count on the event.
		pEvent->AddRef();

		PostMessage(m_hwndEvent, WM_APP_PLAYER_EVENT,
			(WPARAM)pEvent, (LPARAM)meType);
	}

done:
	SafeRelease(&pEvent);
	return S_OK;
}

HRESULT CPlayer::HandleEvent(UINT_PTR pEventPtr)
{
	HRESULT hrStatus = S_OK;
	MediaEventType meType = MEUnknown;

	IMFMediaEvent *pEvent = (IMFMediaEvent*)pEventPtr;

	if (pEvent == NULL)
	{
		return E_POINTER;
	}

	// Get the event type.
	HRESULT hr = pEvent->GetType(&meType);
	if (FAILED(hr))
	{
		goto done;
	}

	// Get the event status. If the operation that triggered the event 
	// did not succeed, the status is a failure code.
	hr = pEvent->GetStatus(&hrStatus);

	// Check if the async operation succeeded.
	if (SUCCEEDED(hr) && FAILED(hrStatus))
	{
		hr = hrStatus;
	}
	if (FAILED(hr))
	{
		goto done;
	}

	switch (meType)
	{
	case MESessionTopologyStatus:
		hr = OnTopologyStatus(pEvent);
		break;

	case MEEndOfPresentation:
		hr = OnPresentationEnded(pEvent);
		break;

	case MENewPresentation:
		hr = OnNewPresentation(pEvent);
		break;

	default:
		hr = OnSessionEvent(pEvent, meType);
		break;
	}

done:
	SafeRelease(&pEvent);
	return hr;
}

//  Release all resources held by this object.
HRESULT CPlayer::Shutdown()
{
	// Close the session
	HRESULT hr = CloseSession();

	// Shutdown the Media Foundation platform
	MFShutdown();

	if (m_hCloseEvent)
	{
		CloseHandle(m_hCloseEvent);
		m_hCloseEvent = NULL;
	}

	return hr;
}

/// Protected methods

HRESULT CPlayer::OnTopologyStatus(IMFMediaEvent *pEvent)
{
	UINT32 status;

	HRESULT hr = pEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status);
	if (SUCCEEDED(hr) && (status == MF_TOPOSTATUS_READY))
	{
		SafeRelease(&m_pVideoDisplay);

		// Get the IMFVideoDisplayControl interface from EVR. This call is
		// expected to fail if the media file does not have a video stream.

		(void)MFGetService(m_pSession, MR_VIDEO_RENDER_SERVICE,
			IID_PPV_ARGS(&m_pVideoDisplay));

		hr = StartPlayback();
	}
	return hr;
}


//  Handler for MEEndOfPresentation event.
HRESULT CPlayer::OnPresentationEnded(IMFMediaEvent *pEvent)
{
	// The session puts itself into the stopped state automatically.
	m_state = Stopped;
	return S_OK;
}

//  Handler for MENewPresentation event.
//
//  This event is sent if the media source has a new presentation, which 
//  requires a new topology. 

HRESULT CPlayer::OnNewPresentation(IMFMediaEvent *pEvent)
{
	IMFPresentationDescriptor *pPD = NULL;
	IMFTopology *pTopology = NULL;

	// Get the presentation descriptor from the event.
	HRESULT hr = GetEventObject(pEvent, &pPD);
	if (FAILED(hr))
	{
		goto done;
	}

	// Create a partial topology.
	hr = CreatePlaybackTopology(m_pSource, pPD, m_hwndVideo, &pTopology);
	if (FAILED(hr))
	{
		goto done;
	}

	// Set the topology on the media session.
	hr = m_pSession->SetTopology(0, pTopology);
	if (FAILED(hr))
	{
		goto done;
	}

	m_state = OpenPending;

done:
	SafeRelease(&pTopology);
	SafeRelease(&pPD);
	return S_OK;
}

//  Create a new instance of the media session.
HRESULT CPlayer::CreateSession()
{
	// Close the old session, if any.
	HRESULT hr = CloseSession();
	if (FAILED(hr))
	{
		goto done;
	}

	assert(m_state == Closed);

	// Create the media session.
	hr = MFCreateMediaSession(NULL, &m_pSession);
	if (FAILED(hr))
	{
		goto done;
	}

	// Start pulling events from the media session
	hr = m_pSession->BeginGetEvent((IMFAsyncCallback*)this, NULL);
	if (FAILED(hr))
	{
		goto done;
	}

	m_state = Ready;

done:
	return hr;
}

//  Close the media session. 
HRESULT CPlayer::CloseSession()
{
	//  The IMFMediaSession::Close method is asynchronous, but the 
	//  CPlayer::CloseSession method waits on the MESessionClosed event.
	//  
	//  MESessionClosed is guaranteed to be the last event that the 
	//  media session fires.

	HRESULT hr = S_OK;

	SafeRelease(&m_pVideoDisplay);

	// First close the media session.
	if (m_pSession)
	{
		DWORD dwWaitResult = 0;

		m_state = Closing;

		hr = m_pSession->Close();
		// Wait for the close operation to complete
		if (SUCCEEDED(hr))
		{
			dwWaitResult = WaitForSingleObject(m_hCloseEvent, 5000);
			if (dwWaitResult == WAIT_TIMEOUT)
			{
				assert(FALSE);
			}
			// Now there will be no more events from this session.
		}
	}

	// Complete shutdown operations.
	if (SUCCEEDED(hr))
	{
		// Shut down the media source. (Synchronous operation, no events.)
		if (m_pSource)
		{
			(void)m_pSource->Shutdown();
		}
		// Shut down the media session. (Synchronous operation, no events.)
		if (m_pSession)
		{
			(void)m_pSession->Shutdown();
		}
	}

	SafeRelease(&m_pSource);
	SafeRelease(&m_pSession);
	m_state = Closed;
	return hr;
}

//  Start playback from the current position. 
HRESULT CPlayer::StartPlayback()
{
	assert(m_pSession != NULL);

	PROPVARIANT varStart;
	PropVariantInit(&varStart);

	HRESULT hr = m_pSession->Start(&GUID_NULL, &varStart);
	if (SUCCEEDED(hr))
	{
		// Note: Start is an asynchronous operation. However, we
		// can treat our state as being already started. If Start
		// fails later, we'll get an MESessionStarted event with
		// an error code, and we will update our state then.
		m_state = Started;
	}
	PropVariantClear(&varStart);
	return hr;
}

//  Start playback from paused or stopped.
HRESULT CPlayer::Play()
{
	if (m_state != Paused && m_state != Stopped)
	{
		return MF_E_INVALIDREQUEST;
	}
	if (m_pSession == NULL || m_pSource == NULL)
	{
		return E_UNEXPECTED;
	}
	return StartPlayback();
}


//  Create a media source from a URL.
HRESULT CreateMediaSource(PCWSTR sURL, IMFMediaSource **ppSource)
{
	MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;

	IMFSourceResolver* pSourceResolver = NULL;
	IUnknown* pSource = NULL;

	// Create the source resolver.
	HRESULT hr = MFCreateSourceResolver(&pSourceResolver);
	if (FAILED(hr))
	{
		goto done;
	}

	// Use the source resolver to create the media source.

	// Note: For simplicity this sample uses the synchronous method to create 
	// the media source. However, creating a media source can take a noticeable
	// amount of time, especially for a network source. For a more responsive 
	// UI, use the asynchronous BeginCreateObjectFromURL method.

	hr = pSourceResolver->CreateObjectFromURL(
		sURL,                       // URL of the source.
		MF_RESOLUTION_MEDIASOURCE,  // Create a source object.
		NULL,                       // Optional property store.
		&ObjectType,        // Receives the created object type. 
		&pSource            // Receives a pointer to the media source.
		);
	if (FAILED(hr))
	{
		goto done;
	}

	// Get the IMFMediaSource interface from the media source.
	hr = pSource->QueryInterface(IID_PPV_ARGS(ppSource));

done:
	SafeRelease(&pSourceResolver);
	SafeRelease(&pSource);
	return hr;
}

HRESULT CreateMediaSource(PCWSTR sURL1, PCWSTR sURL2, IMFMediaSource **ppSource)
{
	MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;

	IMFSourceResolver* pSourceResolver = NULL;
	IUnknown* pSource1 = NULL;
	IUnknown* pSource2 = NULL;
	IMFCollection* pCollection = NULL;

	// Create the source resolver.
	HRESULT hr = MFCreateSourceResolver(&pSourceResolver);
	if (FAILED(hr))
	{
		goto done;
	}

	// Use the source resolver to create the media source.

	// Note: For simplicity this sample uses the synchronous method to create 
	// the media source. However, creating a media source can take a noticeable
	// amount of time, especially for a network source. For a more responsive 
	// UI, use the asynchronous BeginCreateObjectFromURL method.

	hr = pSourceResolver->CreateObjectFromURL(
		sURL1,                       // URL of the source.
		MF_RESOLUTION_MEDIASOURCE,  // Create a source object.
		NULL,                       // Optional property store.
		&ObjectType,        // Receives the created object type. 
		&pSource1            // Receives a pointer to the media source.
		);
	if (FAILED(hr))
	{
		goto done;
	}

	hr = pSourceResolver->CreateObjectFromURL(
		sURL2,                       // URL of the source.
		MF_RESOLUTION_MEDIASOURCE,  // Create a source object.
		NULL,                       // Optional property store.
		&ObjectType,        // Receives the created object type. 
		&pSource2            // Receives a pointer to the media source.
		);
	if (FAILED(hr))
	{
		goto done;
	}

	hr = MFCreateCollection(&pCollection);

	if (FAILED(hr))
	{
		goto done;
	}

	hr = pCollection->AddElement(pSource1);

	if (FAILED(hr))
	{
		goto done;
	}

	hr = pCollection->AddElement(pSource2);

	if (FAILED(hr))
	{
		goto done;
	}

	hr = MFCreateAggregateSource(pCollection, ppSource);

done:
	SafeRelease(&pSourceResolver);
	SafeRelease(&pSource1);
	SafeRelease(&pSource2);
	SafeRelease(&pCollection);
	
	return hr;
}

//  Create an activation object for a renderer, based on the stream media type.

HRESULT CreateMediaSinkActivate(
	IMFStreamDescriptor *pSourceSD,     // Pointer to the stream descriptor.
	HWND hVideoWindow,                  // Handle to the video clipping window.
	IMFActivate **ppActivate
	)
{
	IMFMediaTypeHandler *pHandler = NULL;
	IMFActivate *pActivate = NULL;

	// Get the media type handler for the stream.
	HRESULT hr = pSourceSD->GetMediaTypeHandler(&pHandler);
	if (FAILED(hr))
	{
		goto done;
	}

	// Get the major media type.
	GUID guidMajorType;
	hr = pHandler->GetMajorType(&guidMajorType);
	if (FAILED(hr))
	{
		goto done;
	}

	// Create an IMFActivate object for the renderer, based on the media type.
	if (MFMediaType_Audio == guidMajorType)
	{
		// Create the audio renderer.
		hr = MFCreateAudioRendererActivate(&pActivate);
	}
	else if (MFMediaType_Video == guidMajorType)
	{
		// Create the video renderer.
		hr = MFCreateVideoRendererActivate(hVideoWindow, &pActivate);
	}
	else
	{
		// Unknown stream type. 
		hr = E_FAIL;
		// Optionally, you could deselect this stream instead of failing.
	}
	if (FAILED(hr))
	{
		goto done;
	}

	// Return IMFActivate pointer to caller.
	*ppActivate = pActivate;
	(*ppActivate)->AddRef();

done:
	SafeRelease(&pHandler);
	SafeRelease(&pActivate);
	return hr;
}

HRESULT CreateMediaSinkActivate(
	IMFStreamDescriptor *pSourceSD,     // Pointer to the stream descriptor.
	IMFActivate **ppActivate
	)
{
	IMFMediaTypeHandler *pHandler = NULL;
	IMFActivate *pActivate = NULL;

	// Get the media type handler for the stream.
	HRESULT hr = pSourceSD->GetMediaTypeHandler(&pHandler);
	if (FAILED(hr))
	{
		goto done;
	}

	// Get the major media type.
	GUID guidMajorType;
	hr = pHandler->GetMajorType(&guidMajorType);
	if (FAILED(hr))
	{
		goto done;
	}

	// Create an IMFActivate object for the renderer, based on the media type.
	if (MFMediaType_Audio == guidMajorType)
	{
		// Create the audio renderer.
		hr = MFCreateAudioRendererActivate(&pActivate);
	}
	else if (MFMediaType_Video == guidMajorType)
	{
		hr = S_OK;
	}
	else
	{
		// Unknown stream type. 
		hr = E_FAIL;
		// Optionally, you could deselect this stream instead of failing.
	}
	if (FAILED(hr))
	{
		goto done;
	}

	// Return IMFActivate pointer to caller.
	*ppActivate = pActivate;
	(*ppActivate)->AddRef();

done:
	SafeRelease(&pHandler);
	SafeRelease(&pActivate);
	return hr;
}

// Add a source node to a topology.
HRESULT AddSourceNode(
	IMFTopology *pTopology,           // Topology.
	IMFMediaSource *pSource,          // Media source.
	IMFPresentationDescriptor *pPD,   // Presentation descriptor.
	IMFStreamDescriptor *pSD,         // Stream descriptor.
	IMFTopologyNode **ppNode)         // Receives the node pointer.
{
	IMFTopologyNode *pNode = NULL;

	// Create the node.
	HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &pNode);
	if (FAILED(hr))
	{
		goto done;
	}

	// Set the attributes.
	hr = pNode->SetUnknown(MF_TOPONODE_SOURCE, pSource);
	if (FAILED(hr))
	{
		goto done;
	}

	hr = pNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD);
	if (FAILED(hr))
	{
		goto done;
	}

	hr = pNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD);
	if (FAILED(hr))
	{
		goto done;
	}

	// Add the node to the topology.
	hr = pTopology->AddNode(pNode);
	if (FAILED(hr))
	{
		goto done;
	}

	// Return the pointer to the caller.
	*ppNode = pNode;
	(*ppNode)->AddRef();

done:
	SafeRelease(&pNode);
	return hr;
}

// Add an output node to a topology.
HRESULT AddOutputNode(
	IMFTopology *pTopology,     // Topology.
	IMFActivate *pActivate,     // Media sink activation object.
	DWORD dwId,                 // Identifier of the stream sink.
	IMFTopologyNode **ppNode)   // Receives the node pointer.
{
	IMFTopologyNode *pNode = NULL;

	// Create the node.
	HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &pNode);
	if (FAILED(hr))
	{
		goto done;
	}

	// Set the object pointer.
	hr = pNode->SetObject(pActivate);
	if (FAILED(hr))
	{
		goto done;
	}

	// Set the stream sink ID attribute.
	hr = pNode->SetUINT32(MF_TOPONODE_STREAMID, dwId);
	if (FAILED(hr))
	{
		goto done;
	}

	hr = pNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE);
	if (FAILED(hr))
	{
		goto done;
	}

	// Add the node to the topology.
	hr = pTopology->AddNode(pNode);
	if (FAILED(hr))
	{
		goto done;
	}

	// Return the pointer to the caller.
	*ppNode = pNode;
	(*ppNode)->AddRef();

done:
	SafeRelease(&pNode);
	return hr;
}
//</SnippetPlayer.cpp>

//  Add a topology branch for one stream.
//
//  For each stream, this function does the following:
//
//    1. Creates a source node associated with the stream. 
//    2. Creates an output node for the renderer. 
//    3. Connects the two nodes.
//
//  The media session will add any decoders that are needed.

HRESULT AddBranchToPartialTopology(
	IMFTopology *pTopology,         // Topology.
	IMFMediaSource *pSource,        // Media source.
	IMFPresentationDescriptor *pPD, // Presentation descriptor.
	DWORD iStream,                  // Stream index.
	HWND hVideoWnd)                 // Window for video playback.
{
	IMFStreamDescriptor *pSD = NULL;
	IMFActivate         *pSinkActivate = NULL;
	IMFTopologyNode     *pSourceNode = NULL;
	IMFTopologyNode     *pOutputNode = NULL;

	BOOL fSelected = FALSE;

	HRESULT hr = pPD->GetStreamDescriptorByIndex(iStream, &fSelected, &pSD);
	if (FAILED(hr))
	{
		goto done;
	}

	if (fSelected)
	{
		// Create the media sink activation object.
		hr = CreateMediaSinkActivate(pSD, hVideoWnd, &pSinkActivate);
		if (FAILED(hr))
		{
			goto done;
		}

		// Add a source node for this stream.
		hr = AddSourceNode(pTopology, pSource, pPD, pSD, &pSourceNode);
		if (FAILED(hr))
		{
			goto done;
		}

		// Create the output node for the renderer.
		hr = AddOutputNode(pTopology, pSinkActivate, 0, &pOutputNode);
		if (FAILED(hr))
		{
			goto done;
		}

		// Connect the source node to the output node.
		hr = pSourceNode->ConnectOutput(0, pOutputNode, 0);
	}
	// else: If not selected, don't add the branch. 

done:
	SafeRelease(&pSD);
	SafeRelease(&pSinkActivate);
	SafeRelease(&pSourceNode);
	SafeRelease(&pOutputNode);
	return hr;
}

HRESULT AddBranchToPartialTopology(
	IMFTopology *pTopology,         // Topology.
	IMFMediaSource *pSource,        // Media source.
	IMFPresentationDescriptor *pPD, // Presentation descriptor.
	DWORD iStream,                  // Stream index.
	IMFTopologyNode* pVideoRendererTopologyNode)                 // Window for video playback.
{
	IMFStreamDescriptor *pSD = NULL;
	IMFActivate         *pSinkActivate = NULL;
	IMFTopologyNode     *pSourceNode = NULL;
	IMFTopologyNode     *pOutputNode = NULL;

	BOOL fSelected = FALSE;

	HRESULT hr = pPD->GetStreamDescriptorByIndex(iStream, &fSelected, &pSD);
	if (FAILED(hr))
	{
		goto done;
	}

	if (fSelected)
	{
		CComPtrCustom<IMFMediaTypeHandler> pHandler;
		
		HRESULT hr = pSD->GetMediaTypeHandler(&pHandler);
		if (FAILED(hr))
		{
			goto done;
		}


		// Get the major media type.
		GUID guidMajorType;
		hr = pHandler->GetMajorType(&guidMajorType);
		if (FAILED(hr))
		{
			goto done;
		}

		// Create an IMFActivate object for the renderer, based on the media type.
		if (MFMediaType_Audio == guidMajorType)
		{
			// Create the media sink activation object.
			hr = CreateMediaSinkActivate(pSD, &pSinkActivate);
			if (FAILED(hr))
			{
				goto done;
			}



			// Create the output node for the renderer.
			hr = AddOutputNode(pTopology, pSinkActivate, 0, &pOutputNode);
			if (FAILED(hr))
			{
				goto done;
			}
		}
		else if (MFMediaType_Video == guidMajorType)
		{
			pTopology->AddNode(pVideoRendererTopologyNode);

			pVideoRendererTopologyNode->QueryInterface(IID_PPV_ARGS(&pOutputNode));
		}

		// Add a source node for this stream.
		hr = AddSourceNode(pTopology, pSource, pPD, pSD, &pSourceNode);
		if (FAILED(hr))
		{
			goto done;
		}

		// Connect the source node to the output node.
		hr = pSourceNode->ConnectOutput(0, pOutputNode, 0);
	}
	// else: If not selected, don't add the branch. 

done:
	SafeRelease(&pSD);
	SafeRelease(&pSinkActivate);
	SafeRelease(&pSourceNode);
	SafeRelease(&pOutputNode);
	return hr;
}

//  Create a playback topology from a media source.
HRESULT CreatePlaybackTopology(
	IMFMediaSource *pSource,          // Media source.
	IMFPresentationDescriptor *pPD,   // Presentation descriptor.
	HWND hVideoWnd,                   // Video window.
	IMFTopology **ppTopology)         // Receives a pointer to the topology.
{
	IMFTopology *pTopology = NULL;
	DWORD cSourceStreams = 0;

	// Create a new topology.
	HRESULT hr = MFCreateTopology(&pTopology);
	if (FAILED(hr))
	{
		goto done;
	}




	// Get the number of streams in the media source.
	hr = pPD->GetStreamDescriptorCount(&cSourceStreams);
	if (FAILED(hr))
	{
		goto done;
	}

	// For each stream, create the topology nodes and add them to the topology.
	for (DWORD i = 0; i < cSourceStreams; i++)
	{
		hr = AddBranchToPartialTopology(pTopology, pSource, pPD, i, hVideoWnd);
		if (FAILED(hr))
		{
			goto done;
		}
	}

	// Return the IMFTopology pointer to the caller.
	*ppTopology = pTopology;
	(*ppTopology)->AddRef();

done:
	SafeRelease(&pTopology);
	return hr;
}



//  Create a playback topology from a media source.
HRESULT CreatePlaybackTopology(
	IMFMediaSource *pSource,          // Media source.
	IMFPresentationDescriptor *pPD,   // Presentation descriptor.
	IMFTopologyNode* pVideoRendererTopologyNode,                   // Video renderer activate.
	IMFTopology **ppTopology)         // Receives a pointer to the topology.
{
	IMFTopology *pTopology = NULL;
	DWORD cSourceStreams = 0;

	// Create a new topology.
	HRESULT hr = MFCreateTopology(&pTopology);
	if (FAILED(hr))
	{
		goto done;
	}




	// Get the number of streams in the media source.
	hr = pPD->GetStreamDescriptorCount(&cSourceStreams);
	if (FAILED(hr))
	{
		goto done;
	}

	// For each stream, create the topology nodes and add them to the topology.
	for (DWORD i = 0; i < cSourceStreams; i++)
	{
		hr = AddBranchToPartialTopology(pTopology, pSource, pPD, i, pVideoRendererTopologyNode);
		if (FAILED(hr))
		{
			goto done;
		}
	}

	// Return the IMFTopology pointer to the caller.
	*ppTopology = pTopology;
	(*ppTopology)->AddRef();

done:
	SafeRelease(&pTopology);
	return hr;
}

